package com.example.mykotlin.service

import com.example.mykotlin.base.util.tryOnly
import com.mybatisflex.annotation.Id
import com.mybatisflex.core.BaseMapper
import java.io.Serializable
import java.lang.reflect.Field
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.locks.ReentrantLock
import kotlin.reflect.KClass


class BigBatchSelectFuture<T : Any> private constructor(
    id: Serializable
) {
    private val ids = HashSet<Serializable>()
    private val result = HashMap<Any, T>()

    @Volatile
    private var hasExecute = false

    @Volatile
    private var addIdCount = 0

    @Volatile
    private var hasResult = false

    private val executeLock = ReentrantLock()
    private val resultLock = ReentrantLock()
    private val resultCondition = resultLock.newCondition()

    companion object {
        private const val BATCH_COUNT = 128
        private const val BATCH_WAITE_COUNT = 8
        private var SMALL_BATCH_WAIT_TIME = ConcurrentHashMap<KClass<*>, Int>()
        private const val BATCH_COUNT_WAITE_TIME_LITTLE = 10
        private const val BATCH_COUNT_WAITE_TIME_MORE = 80
        private val CPU_NUMS = Runtime.getRuntime().availableProcessors()
        private val EXECUTOR = Executors.newVirtualThreadPerTaskExecutor()

        private val futuresMapComputeIfAbsentLock = ReentrantLock()
        private val futuresMap = HashMap<KClass<*>, Array<BigBatchSelectFuture<*>?>>()
        private fun futuresMapComputeIfAbsent(key: KClass<*>): Array<BigBatchSelectFuture<*>?> {
            var value = futuresMap[key]
            if (value == null) {
                futuresMapComputeIfAbsentLock.lock()
                try {
                    value = futuresMap[key]
                    if (value == null) {
                        value = arrayOfNulls(CPU_NUMS)
                        futuresMap[key] = value
                    }
                } finally {
                    futuresMapComputeIfAbsentLock.unlock()
                }
            }
            return value!!
        }


        private val futureLocksMapComputeIfAbsentLock = ReentrantLock()
        private val futureLocksMap = HashMap<KClass<*>, Array<ReentrantLock>>()
        private fun futureLocksMapComputeIfAbsent(key: KClass<*>): Array<ReentrantLock> {
            var value = futureLocksMap[key]
            if (value == null) {
                futureLocksMapComputeIfAbsentLock.lock()
                try {
                    value = futureLocksMap[key]
                    if (value == null) {
                        value = Array(CPU_NUMS) { ReentrantLock() }
                        futureLocksMap[key] = value
                    }
                } finally {
                    futureLocksMapComputeIfAbsentLock.unlock()
                }
            }
            return value!!
        }


        private val executeNumMapComputeIfAbsentLock = ReentrantLock()
        private val executeNumMap = ConcurrentHashMap<KClass<*>, Int>()
        private fun executeNumMapComputeIfAbsent(key: KClass<*>): Int {
            var value = executeNumMap[key]
            if (value == null) {
                executeNumMapComputeIfAbsentLock.lock()
                try {
                    value = executeNumMap[key]
                    if (value == null) {
                        value = 1
                        executeNumMap[key] = value
                    }
                } finally {
                    executeNumMapComputeIfAbsentLock.unlock()
                }
            }
            return value!!
        }


        private val idFieldMap = HashMap<KClass<*>, Field>()
        private val idFieldMapLock = ReentrantLock()
        private fun fieldMapComputeIfAbsent(key: KClass<*>): Field {
            var value = idFieldMap[key]
            if (value == null) {
                idFieldMapLock.lock()
                try {
                    value = idFieldMap[key]
                    if (value == null) {
                        value = key.java.declaredFields.filter {
                            it.getAnnotation(Id::class.java) != null
                        }[0]
                        value.isAccessible = true
                        idFieldMap[key] = value
                    }
                } finally {
                    idFieldMapLock.unlock()
                }
            }
            return value!!
        }

        private fun <T : Any> futureInitExecute(
            kClass: KClass<T>, batchSelectFuture: BigBatchSelectFuture<T>, baseMapper: BaseMapper<T>, id: Serializable
        ) {
            tryOnly { Thread.sleep((SMALL_BATCH_WAIT_TIME[kClass] ?: 1).toLong()) }
            batchSelectFuture.executeLock.lock()
            if (!batchSelectFuture.hasExecute) {
                batchSelectFuture.hasExecute = true
                batchSelectFuture.executeLock.unlock()
                val nextWaitTime =
                    if (batchSelectFuture.addIdCount == 1) 1 else if (batchSelectFuture.addIdCount <= BATCH_WAITE_COUNT) BATCH_COUNT_WAITE_TIME_MORE else BATCH_COUNT_WAITE_TIME_LITTLE
                if ((SMALL_BATCH_WAIT_TIME[kClass] ?: 1) != nextWaitTime) {
                    SMALL_BATCH_WAIT_TIME[kClass] = nextWaitTime
                }
                val executeNum = executeNumMap[kClass] ?: 1
                if (executeNum > 1) {
                    executeNumMap[kClass] = executeNum - 1
                }
                futureExecute(kClass, batchSelectFuture, baseMapper, id)
            } else {
                batchSelectFuture.executeLock.unlock()
            }
        }

        private fun <T : Any> futureExecute(
            kClass: KClass<T>, batchSelectFuture: BigBatchSelectFuture<T>, baseMapper: BaseMapper<T>, id: Serializable
        ) {
            batchSelectFuture.resultLock.lock()
            try {
                tryOnly {
                    if (batchSelectFuture.ids.size == 1) {
                        val value = baseMapper.selectOneById(id)
                        batchSelectFuture.result[id] = value
                    } else {
                        val results = baseMapper.selectListByIds(batchSelectFuture.ids)
                        if (results.isNotEmpty()) {
                            val idField = fieldMapComputeIfAbsent(kClass)
                            for (r in results) {
                                batchSelectFuture.result[idField.get(r)!!] = r
                            }
                        }
                    }
                }
                batchSelectFuture.hasResult = true
                batchSelectFuture.resultCondition.signalAll()
            } finally {
                batchSelectFuture.resultLock.unlock()
            }
        }


        fun <T : Any> select(
            kClass: KClass<T>, baseMapper: BaseMapper<T>, id: Serializable
        ): BigBatchSelectFuture<T> {
            val executeIndex = (Thread.currentThread().threadId() % executeNumMapComputeIfAbsent(kClass)).toInt()
            val futureLock = futureLocksMapComputeIfAbsent(kClass)[executeIndex]
            futureLock.lock()
            val batchSelectFutureList = futuresMapComputeIfAbsent(kClass)
            if (batchSelectFutureList[executeIndex] == null) {
                val batchSelectFuture = BigBatchSelectFuture<T>(id)
                batchSelectFutureList[executeIndex] = batchSelectFuture
                futureLock.unlock()
                EXECUTOR.execute { futureInitExecute(kClass, batchSelectFuture, baseMapper, id) }
                return batchSelectFuture
            }
            val batchSelectFuture = batchSelectFutureList[executeIndex] as BigBatchSelectFuture<T>
            val executeLock = batchSelectFuture.executeLock
            executeLock.lock()
            if (batchSelectFuture.ids.size < BATCH_COUNT && !batchSelectFuture.hasExecute) {
                batchSelectFuture.ids.add(id)
                batchSelectFuture.addIdCount++
                executeLock.unlock()
                futureLock.unlock()
                return batchSelectFuture
            } else {
                val next = BigBatchSelectFuture<T>(id)
                batchSelectFutureList[executeIndex] = next
                futureLock.unlock()
                if (!batchSelectFuture.hasExecute) {
                    batchSelectFuture.hasExecute = true
                    executeLock.unlock()
                    val executeNum = executeNumMap[kClass] ?: 1
                    if (executeNum < CPU_NUMS) {
                        executeNumMap[kClass] = executeNum + 1
                    }
                    EXECUTOR.execute { futureExecute(kClass, batchSelectFuture, baseMapper, id) }
                } else {
                    executeLock.unlock()
                }
                EXECUTOR.execute { futureInitExecute(kClass, next, baseMapper, id) }
                return next
            }
        }
    }

    init {
        ids.add(id)
        addIdCount++
    }

    fun getById(id: Serializable): T? {
        if (hasResult) return result[id]
        resultLock.lock()
        try {
            if (!hasResult) resultCondition.await()
            return result[id]
        } finally {
            resultLock.unlock()
        }
    }
}